package CastleDefense;

import java.awt.Graphics;
import java.util.ArrayList;

import javax.swing.ImageIcon;

import org.apache.commons.lang.StringUtils;

import CastleDefense.Managers.EnemyManager;
import CastleDefense.Managers.GameManager;
import CastleDefense.Managers.StateManager;
import CastleDefense.Managers.UpgradeManager;
import CastleDefense.units.PlacedUnit;
import CastleDefense.units.PlacedUnit.StateEffect;

/**
 * An enemy represents any element that appears in the game. The
 * enemy is responsible for resolving collisions and movement
 * based on a set of properties defined either by subclass or externally.
 * 
 * Note that doubles are used for positions. This may seem strange
 * given that pixels locations are integers. However, using double means
 * that an enemy can move a partial pixel. It doesn't of course mean that
 * they will be display half way through a pixel but allows us not lose
 * accuracy as we move.
 * 
 */
public class Enemy
{
    public enum MonsterType { NORMAL, FLYING, GHOST, MAGIC_IMMUNE }
    
    private double x;
    private double y;
    private double dx;
    private double dy;
    private double speed;
    private double lastX = x;
    private double lastY = y;
    private double moveSpeed = 75.0;
    private double maxHealth;
    private double currHealth = 0.0;
    
    private Sprite sprite;
    private String name = "";
    private String size = "";

    private int    col;
    private int    steps;
    private int    armor;
    private int    mArmor;
    private int    bounty;
    
    private int    stepCounter = 0;
    
    protected boolean isDead    = false;
    private boolean   newSprite = false;

    private MonsterList    ML = Game.MonsterList;
    private Monster        me;
    private PlacedUnit     effector;
    private MonsterType    type;
    protected EnemyManager EM;

    private ArrayList<StateEffect>    stateEffects  = new ArrayList<StateEffect>();
    private ArrayList<RockProjectile> rockEffectors = new ArrayList<RockProjectile>();
    private ArrayList<PlacedUnit>     trapEffected  = new ArrayList<PlacedUnit>();
    
    /** State booleans */
    protected boolean isSlowed   = false;
    protected boolean isPoisoned = false;
    protected boolean isSnared   = false;

    /** State durations */
    protected long    slowDuration  = 0;
    protected long    snareDuration = 0;
    protected long    firstSlowed   = 0;
    protected long    firstSnared   = 0;
    protected long    snareTime     = 0;

    /** Animation */
    private ArrayList<Sprite> Frames  = new ArrayList<Sprite>();
    private long      lastFrameChange  = 0;
    private long      frameDuration    = 3 * (10000*10000); // :<
    private int       frameNumber      = 0;

    /**
     * Construct a enemy based on a sprite image and a location.
     *
     * @param ref The reference to the image to be displayed for this enemy
     * @param x The initial x location of this enemy
     * @param y The initial y location of this enemy
     */
    public Enemy(String ref, String name, int x, int y, int col)
    {
        this.me         = ML.getMonsterByName(name);
        this.EM         = new EnemyManager(this);
        
        //init from monsterlist
        this.maxHealth  = me.getHp();
        this.bounty     = me.getBounty();
        this.mArmor     = me.getmArmor();
        this.armor      = me.getArmor();
        this.speed      = me.getSpeed();
        this.type       = me.getMobType();
        this.size       = me.getSize();

        //init from func call vars
        this.currHealth = maxHealth;
        this.name       = name;
        this.col        = col;
        this.dx         = moveSpeed;
        this.dy         = moveSpeed;
        this.x          = x;
        this.y          = y;

        //animation
	this.setAnimation(ref);
    }

    /**
     * Request that this enemy move itself based on a certain amount
     * of time passing.
     *
     * @param delta The amount of time that has passed in milliseconds
     */
    public void move(long delta)
    {
        updatePoisonLogic();

        lastFrameChange += delta;
        if (lastFrameChange > frameDuration)
        {
            // reset our frame change time counter
            lastFrameChange = 0;

            // update the frame
            frameNumber++;
            if (frameNumber >= Frames.size())
            {
                frameNumber = 0;
            }

            this.sprite = Frames.get(frameNumber);
	}

        int myX, myY, myWidth, myHeight;
        myX      = this.getX();
        myY      = this.getY();
        myWidth  = this.getSprite().getWidth();
        myHeight = this.getSprite().getHeight();
        
        // update the location of the enemy based on move speed    
        if(this.getCol() == 1)
        {
            if(myY+myHeight <= 882 && myX < 200)
            {
                y += (speed * dy) / 1000;
            }
            else if(myY+myHeight >= 882 && myX+myWidth <= 630)
            {
                x += (speed * dx) / 1000;
            }
            else if(myX+myWidth >= 630 && myX < 630 && myY >= 462)
            {
                y += (speed * -dy) / 1000;
            }
            else if(myX+myWidth >= 462 && myX < 630 && myY <= 462 && myY >= 400)
            {
                x += (speed * -dx) / 1000;
            }
            else if(myX <= 420 && myY+myHeight >= 252)
            {
                y += (speed * -dy) / 1000;
            }
            else if(myY+myHeight <= 252 && myX+myWidth <= 672+myWidth)
            {
                x += (speed * dx) / 1000;
            }
            else if(myX >= 672 && myX <= 675 && myY+myHeight < 923)
            {
                y += (speed * dy) / 1000;
            }
            else if(myY+myHeight <= 928 && myY+myHeight > 920)
            {
                randomMoveOnWallHit();
            }
        }
        else if(this.getCol() == 2)
        {
            if(myY+myHeight <= 840 && myX < 200)
            {
                y += (speed * dy) / 1000;
            }
            else if(myY+myHeight >= 840 && myX+myWidth <= 588)
            {
                x += (speed * dx) / 1000;
            }
            else if(myX+myWidth >= 588 && myX < 588 && myY >= 504)
            {
                y += (speed * -dy) / 1000;
            }
            else if(myX+myWidth >= 420 && myX < 588 && myY <= 504 && myY >= 400)
            {
                x += (speed * -dx) / 1000;
            }
            else if(myX+myWidth <= 420 && myY+myHeight >= 210)
            {
                y += (speed * -dy) / 1000;
            }
            else if(myY <= 168 && myX <= 714)
            {
                x += (speed * dx) / 1000;
            }
            else if(myX >= 714 && myX <= 717 && myY+myHeight < 923)
            {
                y += (speed * dy) / 1000;
            }
            else if(myY+myHeight <= 928 && myY+myHeight > 920)
            {
                randomMoveOnWallHit();
            }
        }
        else if(this.getCol() == 3)
        {
            if(myY+myHeight <= 798 && myX < 250)
            {
                y += (speed * dy) / 1000;
            }
            else if(myY+myHeight >= 798 && myX <= 462)
            {
                x += (speed * dx) / 1000;
            }
            else if(myX+myWidth >= 462 && myX < 546 && myY >= 546)
            {
                y += (speed * -dy) / 1000;
            }
            else if(myX+myWidth >= 378 && myX < 546 && myY <= 546 && myY >= 400)
            {
                x += (speed * -dx) / 1000;
            }
            else if(myX+myWidth <= 378 && myY+myHeight >= 168)
            {
                y += (speed * -dy) / 1000;
            }
            else if(myY <= 126 && myX <= 756) //->
            {
                x += (speed * dx) / 1000;
            }
            else if(myX >= 756 && myX <= 758 && myY+myHeight < 923)
            {
                y += (speed * dy) / 1000;
            }
            else if(myY+myHeight <= 928 && myY+myHeight > 920)
            {
                randomMoveOnWallHit();
            }
        }
        else if(this.getCol() == 4)
        {
            if(myY <= 714 && myX < 250)
            {
                y += (speed * dy) / 1000;
            }
            else if(myY >= 714 && myX <= 462)
            {
                x += (speed * dx) / 1000;
            }
            else if(myX >= 462 && myX < 504 && myY >= 588)
            {
                y += (speed * -dy) / 1000;
            }
            else if(myX >= 294 && myX < 504 && myY <= 588 && myY >= 400)
            {
                x += (speed * -dx) / 1000;
            }
            else if(myX <= 294 && myY >= 84)
            {
                y += (speed * -dy) / 1000;
            }
            else if(myY <= 84 && myX <= 798)
            {
                x += (speed * dx) / 1000;
            }
            else if(myX >= 798 && myX <= 800 && myY < 880)
            {
                y += (speed * dy) / 1000;
            }
            else if(myY+myHeight <= 928 && myY+myHeight > 920)
            {
                randomMoveOnWallHit();
            }
        }
    }

    double v = 1;
    private void randomMoveOnWallHit()
    {
        x += v;

        if(this.getX() <= 673)
        {
            v = .75;
            GameManager.getInstance().damageDoor();
        }
        else if(this.getX()+this.getSprite().getWidth() >= 841)
        {
            v = -.75;
            GameManager.getInstance().damageDoor();
        }
    }

    private void updatePoisonLogic()
    {
        //This block is exclusive for poison damage, ticks are taken per step
        //42 pixels per step.
        int xDiff = (int)(getX()-lastX);
        int yDiff = (int)(getY()-lastY);

        if(Math.abs(xDiff) == 42)
        {
            steps++;
            lastX = getX();
            StateManager.getInstance().doPoisonTick(this, effector);
        }
        else if(Math.abs(yDiff) == 42)
        {
            steps++;
            lastY = getY();
            StateManager.getInstance().doPoisonTick(this, effector);
        }
    }

    /**
     *
     * @param proj The projectile doing damage to this enemy
     */
    public void damageCalculation(Projectile proj)
    {
        double damage = UpgradeManager.getInstance().getCurrentDamage(proj.getParent());

        for(int i=0;i<proj.getEffects().size();i++)
        {
            this.addState(proj.getEffects().get(i));
        }

        if(stateEffects.contains(StateEffect.POISON))
                this.setPoisonEffector(proj.getParent());

        if(proj.getEffects().contains(StateEffect.SPLASH))
        {
            StateManager.getInstance().doSplashDamage(damage, this, proj.getParent());
        }

        currHealth -= damageWithArmorReduction(proj,damage);
        Game.removeProjectile(proj);
        
        if(currHealth <= 0)
        {
            currHealth = 0;
            this.isDead=true;
            this.isSlowed=false;
            this.isSnared=false;
            this.isPoisoned=false;
            this.trapEffected.clear();
            proj.getParent().addUnitKills();
            Game.removeEnemy(this);
            Game.addGold(bounty);

            if(this==Game.getHoverMob())
                Game.setHoverMob(null);
        }
    }

    /**
     *
     * @param dmg The damage this enemy has taken
     */
    public void doDamageCalculation(double dmg, PlacedUnit p)
    {
        currHealth -= dmg;

        if(currHealth <= 0)
        {
            currHealth = 0;
            this.isDead=true;
            this.isPoisoned=false;
            this.isSlowed=false;
            this.isSnared=false;
            this.trapEffected.clear();
            p.addUnitKills();
            Game.removeEnemy(this);
            Game.addGold(bounty);

            if(this==Game.getHoverMob())
                Game.setHoverMob(null);
        }
    }

    private double damageWithArmorReduction(Projectile proj, double damage)
    {
        double reducedDamage, armorReduction;

        if(proj.getDamageType().equals("Magic"))
            armorReduction = this.mArmor;
        else
            armorReduction = this.armor;

        reducedDamage = damage-(damage*(armorReduction/100));
        
        if(reducedDamage<=0)
            reducedDamage = 1;

        return reducedDamage;
    }

    private void setAnimation(String sprites)
    {
        String[]          spriteArray = StringUtils.splitPreserveAllTokens(sprites, ',');

        for(String spr : spriteArray)
        {
            Sprite temp = new Sprite(new ImageIcon(this.getClass().getResource(GameManager.getInstance().MONSTERS_PATH()+spr)).getImage());
            Frames.add(temp);
        }

        sprite = Frames.get(0);
    }
    
    /**
     * Set the horizontal speed of this enemy
     *
     * @param dx The horizontal speed of this enemy (pixels/sec)
     */
    public void setHorizontalMovement(double dx)
    {
        this.dx = dx;
    }

    /**
     * Set the vertical speed of this enemy
     *
     * @param dx The vertical speed of this enemy (pixels/sec)
     */
    public void setVerticalMovement(double dy)
    {
        this.dy = dy;
    }

    /**
     * Get the horizontal speed of this enemy
     *
     * @return The horizontal speed of this enemy (pixels/sec)
     */
    public double getHorizontalMovement()
    {
        return dx;
    }

    /**
     * Get the vertical speed of this enemy
     *
     * @return The vertical speed of this enemy (pixels/sec)
     */
    public double getVerticalMovement()
    {
        return dy;
    }

    /**
     * Draw this enemy to the graphics context provided
     *
     * @param g The graphics context on which to draw
     */
    public void draw(Graphics g)
    {
        sprite.draw(g, (int) x, (int) y);
    }

    /**
     * Get the x location of this enemy
     *
     * @return The x location of this enemy
     */
    public int getX()
    {
        return (int) x;
    }
    public int getMidX()
    {
        return (int) x + (getSprite().getWidth()/2);
    }

    public void setX(double x)
    {
        this.x = x;
    }

    /**
     * Get the y location of this enemy
     *
     * @return The y location of this enemy
     */
    public int getY()
    {
        return (int) y;
    }
    public int getMidY()
    {
        return (int) y + (getSprite().getHeight()/2);
    }

    public void setY(double y)
    {
        this.y = y;
    }

    /**
     * Get the column of this enemy
     *
     * @return The column of this enemy
     */
    public int getCol()
    {
        return (int) col;
    }

    /**
     * Get the number of steps
     *
     * @return The number of steps enemy has moves
     */
    public int getSteps()
    {
        return (int) steps;
    }

    /**
     * Get the enemy name
     *
     * @return The name of the enemy
     */
    public String getName()
    {
        return (String) name;
    }

    public Enemy getEnemy()
    {
        return this;
    }

    /**
     * Check if the enemy is dead
     *
     * @return true/false
     */
    public boolean isDead()
    {
        return isDead;
    }

    public int getStepCounter()
    {
        return stepCounter;
    }
    
    public void setStepCounter(int stpC)
    {
        this.stepCounter = stpC;
    }
    
    /**
     *
     * @return The enemy's state
     */
    public ArrayList<StateEffect> getStates()
    {
        return stateEffects;
    }

    public void removeState(StateEffect t)
    {
        stateEffects.remove(t);
    }

    public void addState(StateEffect t)
    {
        if(!stateEffects.contains(t))
            stateEffects.add(t);
    }

    public void addRockEffector(RockProjectile rp)
    {
        rockEffectors.add(rp);
    }
    
    public boolean hasRockEffector(RockProjectile rp)
    {
        if(rockEffectors.contains(rp))
            return true;
        else
            return false;
    }
    
    /**
     *
     * @return The enemy's sprite
     */
    public Sprite getSprite()
    {
        return sprite;
    }

    /**
     *
     * @param spr The new enemy sprite
     */
    public void setSprite(Sprite spr)
    {
        this.sprite = spr;
    }
    public void setSprite(Sprite spr, boolean n)
    {
        this.sprite = spr;
        this.newSprite = n;
    }

    public boolean isNewSprite()
    {
        return newSprite;
    }

    /**
     *
     * @return get Enemy current hp
     */
    public double getHP()
    {
        return currHealth;
    }

    /**
     *
     * @return get Max Enemy hp
     */
    public double getMaxHP()
    {
        return maxHealth;
    }

    //A formatted width for enemy hp bar, always ~26px
    public int getHPBR()
    {
        double offset = 0;
        double width  = 0;

        if(Game.getMonsterList().getMonsterByName(name).getSize().equals("BOSS"))
        {
            offset = 146/maxHealth;
            width = (currHealth*offset);
        }
        else if(Game.getMonsterList().getMonsterByName(name).getSize().equals("MINIBOSS"))
        {
            offset = 66/maxHealth;
            width = (currHealth*offset);
        }
        else
        {
            offset = 26/maxHealth;
            width = (currHealth*offset);
        }
        
        return (int)width;
    }

    public int getHPBB()
    {
        double width = 0;
        if(Game.getMonsterList().getMonsterByName(name).getSize().equals("BOSS"))
        {
            width = 148;
        }
        else if(Game.getMonsterList().getMonsterByName(name).getSize().equals("MINIBOSS"))
        {
            width = 68;
        }
        else
        {
            width = 28;
        }

        return (int)width;
    }
    
    /*
     * BELOW: Status effect methods
     */
    public double getMovespeed()
    {
        return moveSpeed;
    }

    public double getSpeed()
    {
        return speed;
    }

    public void setSpeed(double d)
    {
        this.speed = d;
    }

    public void resetSpeed()
    {
        this.speed = ML.getMonsterByName(this.name).getSpeed();
    }

    public void setSlowed(boolean b)
    {
        this.isSlowed = b;
    }

    public boolean isSlowed()
    {
        return isSlowed;
    }

    public void setSnared(boolean b)
    {
        this.isSnared = b;
    }

    public boolean isSnared()
    {
        return isSnared;
    }

    public void setPoisoned(boolean b)
    {
        this.isPoisoned = b;
    }

    public boolean isPoisoned()
    {
        return isPoisoned;
    }

    public void setSlowDuration(long sld)
    {
        this.slowDuration = sld;
    }

    public long getSlowDuration()
    {
        return slowDuration;
    }

    public void setSnareDuration(long snd)
    {
        this.snareDuration = snd;
    }

    public long getSnareDuration()
    {
        return snareDuration;
    }

    public long getSnareTime()
    {
        return snareTime;
    }
    
    public void setSnareTime(long st)
    {
        st+=1000;
        snareTime = st/1000;
    }
    
    public void setFirstSlowed(long fsl)
    {
        this.firstSlowed = fsl;
    }

    public long getFirstSlowed()
    {
        return firstSlowed;
    }

    public void setFirstSnared(long fsn)
    {
        this.firstSnared = fsn;
    }

    public long getFirstSnared()
    {
        return firstSnared;
    }

    public int getBounty()
    {
        return bounty;
    }

    public MonsterType getMobType()
    {
        return type;
    }

    public String getSize()
    {
        return size;
    }
    
    /**
     * @return the trapEffected
     */
    public ArrayList<PlacedUnit> getTrapEffected()
    {
        return trapEffected;
    }

    /**
     * @param trapEffected the trapEffected to set
     */
    public void addTrapEffector(PlacedUnit pu)
    {
        this.trapEffected.add(pu);
    }

    public void setPoisonEffector(PlacedUnit effector)
    {
        this.effector = effector;
    }
}